using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace HadronData
{
    public class Edge
        : Geometry
    {
        private Vector2 m_vecStart, m_vecEnd;
        private Color m_colour;

        public Edge()
        {
            m_vecStart = Vector2.Zero;
            m_vecEnd = Vector2.Zero;
            m_colour = Color.White;
        }

        public Edge(Edge p_edge)
        {
            m_vecStart = p_edge.m_vecStart;
            m_vecEnd = p_edge.m_vecEnd;
            m_colour = p_edge.m_colour;
        }

        public Edge(Vector2 p_vecStart, Vector2 p_vecEnd)
        {
            m_vecStart = p_vecStart;
            m_vecEnd = p_vecEnd;
            m_colour = Color.White;
        }

        public Edge(float p_fStartX, float p_fStartY, float p_fEndX, float p_fEndY)
        {
            m_vecStart.X = p_fStartX;
            m_vecStart.Y = p_fStartY;
            m_vecEnd.X = p_fEndX;
            m_vecEnd.Y = p_fEndY;
            m_colour = Color.White;
        }

        public Edge(Vector2 p_vecStart, Vector2 p_vecEnd, Color p_colour)
        {
            m_vecStart = p_vecStart;
            m_vecEnd = p_vecEnd;
            m_colour = p_colour;
        }

        public Edge(float p_fStartX, float p_fStartY, float p_fEndX, float p_fEndY, Color p_colour)
        {
            m_vecStart.X = p_fStartX;
            m_vecStart.Y = p_fStartY;
            m_vecEnd.X = p_fEndX;
            m_vecEnd.Y = p_fEndY;
            m_colour = p_colour;
        }

        public Edge(Color p_colour)
        {
            m_vecStart = Vector2.Zero;
            m_vecEnd = Vector2.Zero;
            m_colour = p_colour;
        }

        public bool Intersects(Edge p_edge)
        {
            float fDeltaX1 = m_vecEnd.X - m_vecStart.X;
            float fDeltaY1 = m_vecEnd.Y - m_vecStart.Y;
            float fDeltaX2 = p_edge.End.X - p_edge.Start.X;
            float fDeltaY2 = p_edge.End.Y - p_edge.Start.Y;
            float fDenominator = fDeltaY2 * fDeltaX1 - fDeltaX2 * fDeltaY1;

            if (fDenominator == 0.0f)
                return false;

            float fDeltaStartX = m_vecStart.X - p_edge.Start.X;
            float fDeltaStartY = m_vecStart.Y - p_edge.Start.Y;

            // intersection parameter for this line
            float fParam = (fDeltaX2 * fDeltaStartY - fDeltaY2 * fDeltaStartX) / fDenominator;
            if (fParam < 0.0f || fParam > 1.0f)
                return false;

            // intersection parameter for other line
            fParam = (fDeltaX1 * fDeltaStartY - fDeltaY1 * fDeltaStartX) / fDenominator;
            if (fParam < 0.0f || fParam > 1.0f)
                return false;

            return true;
        }

        public bool Intersects(Ray p_ray)
        {
            float fDeltaX1 = m_vecEnd.X - m_vecStart.X;
            float fDeltaY1 = m_vecEnd.Y - m_vecStart.Y;
            float fDeltaX2 = p_ray.Direction.X;
            float fDeltaY2 = p_ray.Direction.Y;
            float fDenominator = fDeltaY2 * fDeltaX1 - fDeltaX2 * fDeltaY1;

            if (fDenominator == 0.0f)
                return false;

            float fDeltaStartX = m_vecStart.X - p_ray.Source.X;
            float fDeltaStartY = m_vecStart.Y - p_ray.Source.Y;

            // intersection parameter for this line
            float fParam = (fDeltaX2 * fDeltaStartY - fDeltaY2 * fDeltaStartX) / fDenominator;
            if (fParam < 0.0f || fParam > 1.0f)
                return false;

            // intersection parameter for ray
            fParam = (fDeltaX1 * fDeltaStartY - fDeltaY1 * fDeltaStartX) / fDenominator;
            if (fParam < 0.0f)
                return false;

            return true;
        }

        public bool Intersects(Edge p_edge, ref Vector2 p_vecIntersectionPoint)
        {
            float fDeltaX1 = m_vecEnd.X - m_vecStart.X;
            float fDeltaY1 = m_vecEnd.Y - m_vecStart.Y;
            float fDeltaX2 = p_edge.End.X - p_edge.Start.X;
            float fDeltaY2 = p_edge.End.Y - p_edge.Start.Y;
            float fDenominator = fDeltaY2 * fDeltaX1 - fDeltaX2 * fDeltaY1;

            if (fDenominator == 0.0f)
                return false;

            float fDeltaStartX = m_vecStart.X - p_edge.Start.X;
            float fDeltaStartY = m_vecStart.Y - p_edge.Start.Y;

            // intersection parameter for this line
            float fParam = (fDeltaX2 * fDeltaStartY - fDeltaY2 * fDeltaStartX) / fDenominator;
            if (fParam < 0.0f || fParam > 1.0f)
                return false;

            // intersection parameter for other line
            fParam = (fDeltaX1 * fDeltaStartY - fDeltaY1 * fDeltaStartX) / fDenominator;
            if (fParam < 0.0f || fParam > 1.0f)
                return false;

            p_vecIntersectionPoint = p_edge.Start + p_edge.Offset * fParam;

            return true;
        }

        public bool Intersects(Ray p_ray, ref Vector2 p_vecIntersectionPoint)
        {
            float fDeltaX1 = m_vecEnd.X - m_vecStart.X;
            float fDeltaY1 = m_vecEnd.Y - m_vecStart.Y;
            float fDeltaX2 = p_ray.Direction.X;
            float fDeltaY2 = p_ray.Direction.Y;
            float fDenominator = fDeltaY2 * fDeltaX1 - fDeltaX2 * fDeltaY1;

            if (fDenominator == 0.0f)
                return false;

            float fDeltaStartX = m_vecStart.X - p_ray.Source.X;
            float fDeltaStartY = m_vecStart.Y - p_ray.Source.Y;

            // intersection parameter for this line
            float fParam = (fDeltaX2 * fDeltaStartY - fDeltaY2 * fDeltaStartX) / fDenominator;
            if (fParam < 0.0f || fParam > 1.0f)
                return false;

            // intersection parameter for ray
            fParam = (fDeltaX1 * fDeltaStartY - fDeltaY1 * fDeltaStartX) / fDenominator;
            if (fParam < 0.0f)
                return false;

            p_vecIntersectionPoint = p_ray.Source + p_ray.Direction * fParam;

            return true;
        }

        public bool Intersects(Circle p_circle)
        {
            return (GetClosestPoint(p_circle.Centre) - p_circle.Centre).LengthSquared() < p_circle.RadiusSquared;
        }

        public void ComputeContactPoints(Edge p_edge, List<ContactPoint> p_listContactPonts)
        {
            Vector2 vecIntersectionPoint = Vector2.Zero;
            if (Intersects(p_edge, ref vecIntersectionPoint))
                p_listContactPonts.Add(new ContactPoint(vecIntersectionPoint, Normal));
        }

        public void ComputeContactPoints(Ray p_ray, List<ContactPoint> p_listContactPonts)
        {
            Vector2 vecIntersectionPoint = Vector2.Zero;
            if (Intersects(p_ray, ref vecIntersectionPoint))
                p_listContactPonts.Add(new ContactPoint(vecIntersectionPoint, Normal));
        }

        public void ComputeContactPoints(Circle p_circle, List<ContactPoint> p_listContactPonts)
        {
            Vector2 vecClosestPoint = GetClosestPoint(p_circle.Centre);

            Vector2 vecOffset = vecClosestPoint - p_circle.Centre;

            if (vecOffset.LengthSquared() < p_circle.RadiusSquared)
                p_listContactPonts.Add(new ContactPoint(vecClosestPoint, this.Normal));
        }

        public void ComputeContactPoints(Polygon p_polygon, List<ContactPoint> p_listContactPonts)
        {
            foreach (Edge edge in p_polygon.Edges)
                ComputeContactPoints(edge, p_listContactPonts);
        }

        public Vector2 GetClosestPoint(Vector2 p_vecTestPoint)
        {
            float fParam = Vector2.Dot(p_vecTestPoint - m_vecStart, Direction);
            if (fParam <= 0.0f)
                return m_vecStart;

            Vector2 vecOffset = m_vecEnd - m_vecStart;
            float fLength = vecOffset.Length();

            if (fParam >= fLength)
                return m_vecEnd;
            else
                return m_vecStart + vecOffset * fParam / fLength;
        }

        public void Transform(Transformation p_transformation, Edge p_edgeSource)
        {
            m_vecStart = p_transformation.Apply(p_edgeSource.Start);
            m_vecEnd = p_transformation.Apply(p_edgeSource.End);
        }

        public void Draw(SpriteBatch p_spriteBatch)
        {
            Vector2 vecOrigin = new Vector2(8.0f);
            Vector2 vecOffset = m_vecEnd - m_vecStart;
            float fLength = vecOffset.Length();

            float fAngle = (float)Math.Atan2(vecOffset.Y, vecOffset.X);

            Vector2 vecDirection = vecOffset;
            vecDirection.Normalize();

            Texture2D textureGlowLine = AssetManager.Textures.GlowLine;
            for (float fStep = 0.0f; fStep < fLength + 1.0f; fStep += 2.0f)
                p_spriteBatch.Draw(textureGlowLine, m_vecStart + vecDirection * fStep - vecOrigin, m_colour);
        }

        public Vector2 Start
        {
            get { return m_vecStart; }
            set { m_vecStart = value; }
        }

        public Vector2 End
        {
            get { return m_vecEnd; }
            set { m_vecEnd = value; }
        }

        public Color Colour
        {
            get { return m_colour; }
            set { m_colour = value; }
        }

        [ContentSerializerIgnore]
        public Vector2 Midpoint
        {
            get { return (m_vecStart + m_vecEnd) * 0.5f; }
            set
            {
                Vector2 vecOffset = value - Midpoint;
                m_vecStart += vecOffset;
                m_vecEnd += vecOffset;
            }
        }

        public Vector2 Offset
        {
            get { return m_vecEnd - m_vecStart; }
        }

        public Vector2 Direction
        {
            get
            {
                Vector2 vecDirection = m_vecEnd - m_vecStart;
                vecDirection.Normalize();
                return vecDirection;
            }
        }

        public float Length
        {
            get { return (m_vecEnd - m_vecStart).Length(); }
        }

        public Vector2 Normal
        {
            get
            {
                Vector2 vecDirection = Direction;
                return new Vector2(vecDirection.Y, -vecDirection.X);
            }
        }

        #region Geometry Interface

        public bool Intersects(Geometry p_geometry)
        {
            if (p_geometry is Edge)
            {
                Edge edge = (Edge)p_geometry;
                return Intersects(edge);
            }
            else if (p_geometry is Circle)
            {
                Circle circle = (Circle)p_geometry;
                return Intersects(circle);
            }
            else if (p_geometry is Polygon)
            {
                Polygon polygon = (Polygon)p_geometry;
                return polygon.Intersects(this);
            }
            else
                return false;
        }

        public void ComputeContactPoints(Geometry p_geometry,
            List<ContactPoint> p_listContactPoints)
        {
            if (p_geometry is Edge)
            {
                Edge edge = (Edge)p_geometry;
                ComputeContactPoints(edge, p_listContactPoints);
            }
            else if (p_geometry is Circle)
            {
                Circle circle = (Circle)p_geometry;
                ComputeContactPoints(circle, p_listContactPoints);
            }
            else if (p_geometry is Polygon)
            {
                Polygon polygon = (Polygon)p_geometry;
                ComputeContactPoints(polygon, p_listContactPoints);
            }
        }

        public void Transform(Transformation p_transformation)
        {
            m_vecStart = p_transformation.Apply(m_vecStart);
            m_vecEnd = p_transformation.Apply(m_vecEnd);
        }

        public void Transform(Transformation p_transformation, Geometry p_geometrySource)
        {
            if (!(p_geometrySource is Edge))
                throw new Exception("Source Geometry must be a Edge");
            Edge edge = (Edge)p_geometrySource;
            Transform(p_transformation, edge);
        }

        public Geometry TransformCopy(Transformation p_transformation)
        {
            return new Edge(
                p_transformation.Apply(m_vecStart),
                p_transformation.Apply(m_vecEnd),
                m_colour);
        }

        public void ExtendBounds(ref Bounds p_bounds)
        {
            p_bounds.ExtendTo(m_vecStart);
            p_bounds.ExtendTo(m_vecEnd);
        }

        public Circle GetBoundingCircle(Vector2 p_vecOrigin)
        {
            Circle circle = new Circle(p_vecOrigin, 0.0f);
            circle.Radius = Math.Max(
                (m_vecStart - p_vecOrigin).Length(),
                (m_vecEnd - p_vecOrigin).Length());
            return circle;
        }

        public Bounds Bounds
        {
            get
            {
                Bounds bounds = new Bounds();
                bounds.SetEmpty();
                bounds.ExtendTo(m_vecStart);
                bounds.ExtendTo(m_vecEnd);
                return bounds;
            }
        }

        #endregion
    }
}
